/**
 * Copyright (c) 2015-2019, Golly Hu 胡高 (3722711@qq.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package cn.hg.jboot.core.directive;

import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.SqlPara;
import com.jfinal.plugin.activerecord.sql.JbsaDirectiveBase;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.Const;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.Id;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;

import io.jboot.web.directive.annotation.JFinalDirective;

/**
 * 扩展JFinal的SQL模板的para指令，支持默认值
 * 
 * <pre>
 * SQL模板：WHERE endDate >= #paraDef(endDate, '9999-12-31 23:59:59')
 * 参数：endDate = null
 * 解析结果：WHERE endDate >= ?
 * </pre>
 * 
 * @author 胡高
 */
@JFinalDirective(ParaDefDirective.DIRECTIVE_NAME)
public class ParaDefDirective extends JbsaDirectiveBase {

    /**
     * 指令名称
     */
    public static final String DIRECTIVE_NAME = "paraDef";

    private static boolean checkParaAssigned = true;

    public static void setCheckParaAssigned(boolean checkParaAssigned) {
        ParaDefDirective.checkParaAssigned = checkParaAssigned;
    }

    private int index = -1;
    private String paraName = null;

    @Override
    public void onRender(Env env, Scope scope, Writer writer) {
        SqlPara sqlPara = (SqlPara)scope.get(SQL_PARA_KEY);

        if (sqlPara == null) {
            throw new TemplateException("#" + DIRECTIVE_NAME + " directive invoked by getSqlPara(...) method only",
                this.location);
        }

        this.write(writer, "?");

        if (this.index == -1) {

            // issue: http://www.jfinal.com/feedback/1832
            if (ParaDefDirective.checkParaAssigned && this.paraName != null && !scope.exists(this.paraName)) {
                throw new TemplateException("The parameter \"" + this.paraName + "\" must be assigned", this.location);
            }

            // 屏蔽掉原代码
            // sqlPara.addPara(exprList.eval(scope));
            Object[] array = this.exprList.evalExprList(scope);

            if (array[0] != null && array[0] instanceof String && StrKit.notBlank((String)array[0])) {
                // 校验的同时，String类型参数不能为空字串
                // 第一个参数不为空则注册参数到sqlPara
                sqlPara.addPara(array[0]);
            } else if (array.length > 1) {
                // 将Default值注册到sqlPara
                sqlPara.addPara(array[array.length - 1]);
            } else {
                throw new TemplateException(
                    "The parameter \"" + this.paraName + "\" must be assigned or give a default value", this.location);
            }

        } else {
            Object[] paras = (Object[])scope.get(PARA_ARRAY_KEY);

            if (paras == null) {
                throw new TemplateException("The #" + DIRECTIVE_NAME + "(" + this.index
                    + ") directive must invoked by getSqlPara(String, Object...) method", this.location);
            }

            if (this.index >= paras.length) {
                throw new TemplateException(
                    "The index of #" + DIRECTIVE_NAME + " directive is out of bounds: " + this.index, this.location);
            }

            sqlPara.addPara(paras[this.index]);
        }

    }

    @Override
    public void setExprList(ExprList exprList) {

        if (exprList.length() == 0) {
            throw new ParseException("The parameter of #" + DIRECTIVE_NAME + " directive can not be blank",
                this.location);
        }

        if (exprList.length() == 1) {
            Expr expr = exprList.getExpr(0);

            if (expr instanceof Const && ((Const)expr).isInt()) {
                this.index = ((Const)expr).getInt();

                if (this.index < 0) {
                    throw new ParseException("The index of para array must greater than -1", this.location);
                }

            }

        }

        if (ParaDefDirective.checkParaAssigned && exprList.getLastExpr() instanceof Id) {
            Id id = (Id)exprList.getLastExpr();
            this.paraName = id.getId();
        }

        this.exprList = exprList;
    }

}
